unit cuSplitLine;

interface

uses Classes;

(*
Ideas:
Ignore Line Char(s): give a specific char/string, that if ever at the start of a line, it is ignored
*)
type
  tSplitLine  = class( TObject )
  private
    m_Words             : TStringList;
    m_Errors            : TStringList;
    m_NextWordID        : integer;
    m_NextWord          : string;
    m_LineToSplit       : string;
    m_Seperator         : string;
    m_InternalSeperator : string;
    m_GroupingChar      : string;
    m_ReplaceTabs       : boolean;
    function GetWordCount : integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure clearLists;
    procedure setChar( nSepChar, nIntSepChar, nGrpChar:string);
    procedure addWord(nStr:string);
    procedure deleteWord(Index:integer);
    procedure explodeString(SplitStr:string);
    function combineWords(nSeperator:string):string;

    function toInt(Index:integer):integer;
    function toStr (Index:integer):string;
    function toBool(Index:integer):boolean;
    function toFloat(Index:integer):real;
    function partialCopy(SplitString:string):string;
    function charReplace(SplitString:string;findChar,replaceChar:char):string;
    function findWord(WordToFind:string):integer;
    function getNextWord():boolean; //Selects next word from Current Line and stores in m_NextWord/NextWord, returns FALSE if no more words to cycle.
    function importWords(var nWords:TStringList):boolean;
    function exportWords():TStringList;
    procedure addError(error:string);

    property Count : integer read GetWordCount;
    property Words : TStringList read m_Words;
    property Errors : TStringList read m_Errors;
    property NextWord : string read m_NextWord;
    property NextWordID : integer read m_NextWordID;
    property Seperator : string read m_InternalSeperator write m_InternalSeperator;
    property GroupingChar : string read m_GroupingChar write m_GroupingChar;
    property ReplaceTabs  : boolean read m_ReplaceTabs write m_ReplaceTabs;
  end;

implementation

{=====================================================}
uses SysUtils, Messages, dialogs, math;

    {==============================================================}
    {  TSplitLine                                                  }
    {==============================================================}

constructor TSplitLine.Create;
begin
  inherited Create;

  m_Words             := TStringList.Create;
  m_Errors            := TStringList.Create;
  m_Seperator         := ' '; //Default Seperator value
  m_InternalSeperator := '^ '; //Default Internal Seperator value, followed by the char to replace it with (default is Space)
  m_GroupingChar      := '"'; //Default Internal Grouping value
  m_NextWordID        := -1;
  m_NextWord          := '';
  m_ReplaceTabs       := TRUE;
end;

destructor TSplitLine.Destroy;
begin
  FreeAndNil( m_Words );
  FreeAndNil( m_Errors );

  inherited Destroy;
end;

procedure TSplitLine.clearLists;
begin
  m_Words.Clear;
  m_Errors.Clear;
  m_NextWordID        := -1;
  m_NextWord          := '';
end;

procedure TSplitLine.SetChar( nSepChar, nIntSepChar, nGrpChar:string);
begin
  if (nSepChar <> '') then
    m_Seperator         := nSepChar; //Default Seperator value (e.g. "a word")
  if (nIntSepChar <> '') then
    m_InternalSeperator := nIntSepChar; //Default Internal Seperator value (e.g. "a^word" )
  if (nGrpChar <> '') then
    m_GroupingChar      := nGrpChar; //Default Internal Grouping value
end;

procedure TSplitLine.AddWord(nStr:string);
begin
  m_Words.Add( nStr );
end;

procedure TSplitLine.DeleteWord(Index:integer);
begin
  if (Index < m_Words.Count) then
    m_Words.Delete(Index);
end;

procedure TSplitLine.explodeString(SplitStr:string);
var
  L  : integer;
  nStr : string;
  nSeperator:string;
begin
  clearLists; //clear the words

  if (Length( SplitStr ) <= 0) then exit;

  nStr:= '';
  nSeperator := m_Seperator;

  m_LineToSplit := Trim(SplitStr); // replace tabs with spaces, will need a way to replace multiple spaces with single space

  if (m_ReplaceTabs) then
    m_LineToSplit := CharReplace(SplitStr,#9,#32); // replace tabs with spaces, will need a way to replace multiple spaces with single space

  for L := 1 to Length(m_LineToSplit) do (* keep going till the end of the line *)
  begin
    if (m_LineToSplit[L] = m_GroupingChar) then
    begin
      if (nSeperator = m_Seperator) then
        nSeperator := m_GroupingChar
      else
        nSeperator := m_Seperator;
    end;

    if (m_LineToSplit[L] <> nSeperator)and(m_LineToSplit[L] <> m_GroupingChar) then
      nStr := nStr + m_LineToSplit[L]; (* add letter to word *)

    if (m_LineToSplit[L] = nSeperator)or(L = length( m_LineToSplit )) then
    begin
      if (Length(nStr) > 0) then
      begin
        AddWord(nStr);
        nStr:= '';
      end;
    end;
  end;(* for *)

  if (nSeperator = m_GroupingChar) then
    addError('!CRITICAL! Group Char still active. '+m_LineToSplit);
end; (* procedure *)

function TSplitLine.combineWords(nSeperator:string):string;
var
  W  : integer;
begin
  result:= '';

  for W := 0 to Count-1 do (* keep going till the end of the line *)
  begin
    result := result + toStr(W);
    if (W < Count-1) then
      result := result + nSeperator;
  end;(* for *)
end; (* procedure *)

function TSplitLine.toInt(Index:integer):integer;
var
  Number, AnError:integer;
begin
  result := 0;

  if (Index <= m_Words.Count) then
  begin
    val(m_Words[Index], Number, AnError);
    if (AnError = 0) then result := Number
  end;
end;

function TSplitLine.toStr(Index:integer):string;
begin
  if (Index <= Count) then
    result := CharReplace( m_Words[Index], m_InternalSeperator[1], m_InternalSeperator[2] )
  else
    result := 'Index ['+IntToStr(Index)+']';
end;

function TSplitLine.toBool(Index:integer):boolean;
begin
  result := FALSE;
  if (Index <= Count) then
    if (Uppercase(m_Words[Index]) = 'FALSE') then result := FALSE
    else                                          result := TRUE;
end;

function TSplitLine.toFloat(Index:integer):real;
begin
  result := 0;

  if (Index <= m_Words.Count) then
  begin
    result := StrToFloat( m_Words[Index] );
//    if (AnError = 0) then result := Number
  end;
end;

function TSplitLine.partialCopy(SplitString:string):string; //What does this do?
begin
  PartialCopy := Copy(m_LineToSplit, Length(SplitString)+1, Length(m_LineToSplit) );
end;

function TSplitLine.charReplace(SplitString:string;findChar,replaceChar:char):string;
var I : integer; nStr : string;
begin
  nStr := SplitString;
  for I := 0 to Length(nStr) - 1 do
    if (nStr[I] = findChar) then nStr[I] := replaceChar;

  result := nStr;
end;

function TSplitLine.FindWord(WordToFind:string):integer;
var
  W    : integer;
begin
  FindWord := -1;
  for W := 0 to Count - 1 do
  begin
    if (toStr(W) = WordToFind) then
    begin
      FindWord := W;
      exit;
    end;
  end;
end;

function TSplitLine.GetNextWord():boolean;
begin
  result := FALSE;

  if (m_NextWordID >= m_Words.Count-1) then
  begin
    m_NextWordID := -1; //Reset this ready for the next line (if exist), nearly missed this...
    exit;
  end;

  result := TRUE;

  Inc( m_NextWordID );

  m_NextWord := m_Words[ m_NextWordID ];
end;

function TSplitLine.GetWordCount : integer;
begin
  result := m_Words.Count;
end;

function TSplitLine.ImportWords(var nWords:TStringList):boolean;
begin
  clearLists();

  result := TRUE;

  m_Words := nWords;
end;

function TSplitLine.ExportWords():TStringList; //Fills up a TStringList with whats in the m_Xxxx list.
begin
  result := m_Words;
end;

procedure TSplitLine.AddError(error: string);
begin
  m_Errors.Add(error);
end;


end.
